<?php
/**
 * 
 * @todo description of this class
 * 
 * @author Alton Crossley <crossleyframework@nogahidebootstrap.com>
 * @package Crossley Framework
 *  
 * @copyright Copyright (c) 2003-2009, Nogaide BootStrap INC. All rights reserved.
 * @license BSD http://opensource.org/licenses/bsd-license.php
 * @version $Id:$
 * 
 */
/**
 * 
 * This is meant to be a focused front controler serving up
 * class based content or static file content
 * 
 * 
 */
/**
 * basic front Broker
 *
 */
class X_Broker
{
    private static $_aRegistry = array();
    /**
     * singelton front Broker instance
     *
     * @var X_Controller_Front
     */
    private static $_oInstance;
    /**
     * path for html content (mock content)
     * suggested to be outside the publicly accessable
     *
     * @var string
     */
    public static $sContentPath = '../content/';
    /**
     * path to Broker and handler classes
     *
     * @var string
     */
    public static $sClassPath = 'application';
    /**
     * allow html to hold the place of a contoler action
     *
     * @var bool
     */
    private static $_bHtmlFileContent = false;
    /**
     * allow printing of errors and messages
     *
     * @var bool
     */
    private static $_bDebug = false;
    /**
     * allow use of the Broker Generator
     *
     * @var unknown_type
     */
    private static $_bGenerate = false;
    /**
     * incoming event
     *
     * @var X_Broker_Event
     */
    private static $oEvent;
    /**
     * response singelton
     *
     * @var X_Broker_Response
     */
    public static $oResponse;
    /**
     * when errors happen this fires
     *
     * @var X_Broker_Error
     */
    protected static $_oErrorHandler;
    /**
     * current query string
     *
     * @var string
     */
    public static $sQuery;
    /**
     * headers to be sent to the client on the current request
     *
     * @var array
     */
    private static $_aHeaders = array();
    /** 
     * the type of response
     * 
     * @var constant X_Broker_Response Encode Type
     */
    private static $_cResponseType;
    /**
     * @param $_cResponseType the $_cResponseType to set
     */
    static public function setResponseType($_cResponseType, $bOverride = false)
    {
        if (empty(X_Broker::$_cResponseType) || $bOverride)
        {
            self::$_cResponseType = $_cResponseType;
            return;
        }
        // if the response type has changed we are now complex.
        if ($_cResponseType != self::$_cResponseType)
        {
            self::$_cResponseType = X_Broker_Response::ENCODE_COMPLEX;
        }
    }
    /**
     * @return the $_cResponseType
     */
    static public function getResponseType()
    {
        if (empty(X_Broker::$_cResponseType))
        {
            return X_Broker_Response::ENCODE_HTML;
        }
        return X_Broker::$_cResponseType;
    }
    /**
     * register a value in the Broker registry
     * 
     * @param string $sKey
     * @param unknown_type $xValue
     * @return unknown_type 
     */
    static function registerValue($sKey, $xValue)
    {
        self::$_aRegistry[$sKey] = $xValue;
    }
    /**
     * return all the registed values
     * 
     * @return array 
     */
    static function getRegisteredValues()
    {
        return self::$_aRegistry;
    }
    /**
     * retrieve a value from the Broker registry
     * 
     * @param string $sKey
     * @return unknown_type 
     */
    static function getRegistered($sKey)
    {
        if (array_key_exists($sKey, self::$_aRegistry))
        {
            return self::$_aRegistry[$sKey];
        }
        return false;
    }
    /**
     * @return unknown_type
     */
    public static function getGenerate()
    {
        return self::$_bGenerate;
    }
    /**
     * @param bool $_bGenerate
     */
    public static function setGenerate($_bGenerate = true)
    {
        self::$_bGenerate = $_bGenerate;
    }
    /**
     * @return bool
     */
    public function getHtmlFileContent()
    {
        return self::$_bHtmlFileContent;
    }
    /**
     * @param bool $_bHtmlFileContent
     */
    public function setHtmlFileContent($bHtmlFileContent = true)
    {
        self::$_bHtmlFileContent = $bHtmlFileContent;
    }
    /**
     * @return bool
     */
    public function isDebug()
    {
        return self::$_bDebug;
    }
    /**
     * @param bool $_bDebug
     */
    public function setDebug($bDebug = true)
    {
        self::Debug($bDebug);
    }
    static public function Debug($bDebug = true)
    {
        self::$_bDebug = $bDebug;
        if (self::$_oErrorHandler)
        {
            self::$_oErrorHandler->bDebug = $bDebug;
        }
    }
    static public function callLoopback($sRelativeUrl, $aEventData = array())
    {
        if ($aEventData instanceof X_Broker_Event)
        {
            $oEvent = $aEventData;
            $aEventData = $oEvent->getData();
        }
        else 
        {
            $oEvent = new X_Broker_Event(false);
            $oEvent->setData($aEventData);
        }
           
        $oEvent->setUri($sRelativeUrl);
        return self::getResponse($oEvent);
    }
    static public function getResponse(X_Broker_Event_Interface $oEvent = null)
    {
        $oInstnace = self::getInstance();
        return $oInstnace->respond($oEvent);
    }
    /**
     * get the singelton instance
     *
     * @return X_Broker
     */
    static public function getInstance()
    {
        if (self::$_oInstance instanceof X_Broker)
        {
            return self::$_oInstance;
        }
        self::$_oInstance = new X_Broker();
        return self::$_oInstance;
    }
    /**
     * standard constructor
     */
    function __construct()
    {
        // put any functions we need here
        // make sure the response object is available
        if (! self::$oResponse instanceof X_Broker_Response)
        {
            self::$oResponse = new X_Broker_Response();
        }
        if (! self::$oEvent instanceof X_Broker_Event)
        {
            self::$oEvent = new X_Broker_Event();
        }
        self::$_oErrorHandler = new X_Broker_Error($this);
    }
    /**
     * adds a error string to the error singelton
     *
     * @param unknown_type $sError
     */
    static public function addError($sError)
    {
        self::$oResponse->addError($sError);
    }
    /**
     * handle input event
     * if no event is given, it will be obtained from the
     * static getClientParameters method
     *
     * @param Array
     * @return void
     */
    public function respond($oEvent = false)
    {
        if ($oEvent)
        {
            self::$oEvent = $oEvent;
        }
        else
        {
            $this->getEvent();
        }
        $sControllerURN = self::$oEvent->getStep();
        if ($sControllerURN)
        {
            $sClassName = X_String::toCapWords($sControllerURN) . 'Controller';
            $sHtmlFileName = self::$sContentPath . $sControllerURN . X_String::toCapWords(self::getPath()) . X_String::toCapWords(self::getData()) . '.html';
        }
        else
        {
            $sClassName = 'IndexController';
            $sHtmlFileName = self::$sContentPath . $sClassName . X_String::toCapWords(self::getPath()) . X_String::toCapWords(self::getData()) . '.html';
        }
        try
        {
            if (self::$_bHtmlFileContent && file_exists($sHtmlFileName))
            {
                $xResponse = file_get_contents($sHtmlFileName);
                self::setResponseType(X_Broker_Response::ENCODE_HTML);
            }
            elseif (class_exists($sClassName, true) && in_array('X_Controller_Interface', class_implements($sClassName)))
            {
                if (! X_Broker::$_bDebug) ob_start();
                // get an instance of the controller
                $oEventHandler = new $sClassName();
                if (method_exists($oEventHandler, 'setGenerate'))
                {
                    $oEventHandler->setGenerate();
                }
                if (method_exists($oEventHandler, 'setGenerate'))
                {
                    $oEventHandler->setGenerate();
                }
                $oEventHandler->setHtmlFileContent();
                // get the response to the the event 
                $xResponse = $oEventHandler->respond(self::$oEvent);
                if (! X_Broker::$_bDebug) $sDross = ob_get_clean();
            }
            else
            {
                self::setResponseType(X_Broker_Response::ENCODE_HTML);
                if (self::$_bGenerate)
                {
                    // add the class name to the data
                    self::$oEvent->setData(array('gen_class_name' => $sClassName , 'gen_file_name' => $sHtmlFileName), true);
                    // reset the path to handle everything in order
                    self::$oEvent->resetPath();
                    // DEVELOPMENT GENERATION
                    $oDevController = new X_Controller_Generator();
                    $oDevController->bHtmlContent = self::$_bHtmlFileContent;
                    $xResponse = $oDevController->respond(self::$oEvent);
                    $xResponse;
                }
                elseif (self::$_bDebug)
                {
                    $sError = "ERROR: <b>{$sClassName}::respond()</b> is NOT callable";
                    if (self::$_bHtmlFileContent) $sError .= " and <b>{$sHtmlFileName}</b> is not found.<br>";
                    throw new X_Controller_Exception($sError);
                }
                else
                {
                    throw new X_Controller_Exception('unable to control event');
                }
            }
        }
        catch (Exception $e)
        {
            // save the updated error
            self::$oResponse->addError($e->getMessage() . "\n" . $e->getTraceAsString());
        }
        //		$aData = self::$oEvent->getData();
        // check for JSON encoding, errors will autmaticall be handled if JSON is chosen
        if (self::$oEvent->getData('encode') == 'json')
        {
            switch (self::getResponseType())
            {
                case X_Broker::RESPONSE_HTML:
                    self::$oResponse->setHtml($xResponse);
                    break;
                case X_Broker::RESPONSE_JSON:
                case X_Broker::RESPONSE_COMPLEX:
                default:
                    self::$oResponse->setData($xResponse);
                    break;
            }
            return self::$oResponse;
        }
        else
        {
            if (self::$oResponse->hasErrors())
            {
                if (self::$_bDebug)
                {
                    return str_replace("\n", "<br>\n", implode('\n', self::$oResponse->getErrors()) . "<hr>\n") . "<br><small><b>TIP:</b>Enable generation on your broker using '\$oBroker->setGeneration();' " . " to allow code generation.</small><br>";
                }
                else
                {
                    // the error handler will handle proper formating for the given requiest beyond JSON
                    return self::$_oErrorHandler->respond(self::$oResponse->getErrors());
                }
            }
            else
            {
                // handle the response type
                switch (self::getResponseType())
                {
                    // encode to JSON
                    case X_Broker_Response::ENCODE_JSON:
                        $this->_setHeaders();
                        self::$oResponse->setData($xResponse);
                        return self::$oResponse;
                        break;
                    // Return just a simpel string assuming HTML getting the dependencies
                    case X_Broker_Response::ENCODE_HTML:
                        // we leave the defalut PHP headers
                        $xResponse = self::$oResponse->getDependencyString() . $xResponse;
                    // leave output alone as it is in a custom format
                    case X_Broker_Response::ENCODE_COMPLEX:
                    default:
                        $this->_setHeaders();
                        return $xResponse;
                        break;
                }
            }
        }
    }
    /**
     * assign an error handler controler
     *
     * @param X_Controller_Interface $oErrorHandler
     */
    public function setErrorHandler(X_Controller_Interface $oErrorHandler)
    {
        self::$_oErrorHandler = $oErrorHandler;
    }
    static public function setPath($sPath)
    {
        if (empty($sPath))
        {
            trigger_error("path is empty (if you are using realpath it may not exist)", E_USER_WARNING);
            return false;
        }
        if (is_dir($sPath))
        {
            self::$sClassPath = realpath($sPath) . DIRECTORY_SEPARATOR;
        }
        else
        {
            throw new X_Controller_Exception($sPath . ' does not exist');
        }
        X_Loader::addIncludePath($sPath, true);
    }
    static public function &getEvent()
    {
        // check to see if we have an Event Instance
        if (! isset(self::$oEvent))
        {
            // build one if we dont
            self::$oEvent = new X_Broker_Event();
            // Broker Event automatically scraps the path and GP data.	        
        }
        return self::$oEvent;
    }
    /**
     * return the path array
     *
     * @return Array
     */
    static public function getPath()
    {
        return self::$oEvent->getPath();
    }
    static public function getData()
    {
        return self::$oEvent->getData();
    }
    static public function getHistory()
    {
        return self::$oEvent->getHistory();
    }
    /**
     * return the equivelent of ../ from the URL
     * @todo make getParentURL work
     *
     * @return string
     */
    static public function getParentURL()
    {
        $aPath = self::getHistory();
        // remove the last non empty item
        $EndItem = array_pop($aPath);
        if (empty($aPath)) array_pop($aPath);
        // return URL
        return self::$sBaseURL . '/' . implode('/', $aPath) . '/';
    }
    /**
     * return the last processed node in the event
     * @todo make sure whereAmI() is working as purposed
     *
     * @param array $aEvent
     * @return string
     */
    static public function whereAmI()
    {
        $aHistory = self::getHistory();
        // if you havent been anywhere you are at the beginning
        if (empty($aHistory) || ! is_array($aHistory))
        {
            return false;
        }
        // return the last processed node in the path
        return end($aHistory);
    }
    /**
     * add data to the headers
     * @param $sHeader
     * @return array
     */
    static public function setHeader($sHeader)
    {
        return array_push(self::$_aHeaders, $sHeader);
    }
    /**
     * used for responses that need to js forward the whole page
     * works well in ajax included content to ensure the whole page gets forwarded
     * @param string $sUrl
     * @return string
     */
    static public function forwardJs($sUrl)
    {
        self::setResponseType(X_Broker_Response::ENCODE_HTML, true);
        return sprintf("\n<script type='text/javascript'>\n\ttop.location.href = '%s';\n</script>\n", $sUrl);
    }
    protected function _setHeaders()
    {
        foreach (self::$_aHeaders as $sHeader)
        {
            header($sHeader);
        }
    }
}